﻿using NBitcoin.Altcoins.Elements;
using NBitcoin.RPC;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using NBitcoin.Altcoins;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
using Xunit;
using Encoders = NBitcoin.DataEncoders.Encoders;
using System.Threading;
using NBitcoin.Logging;
using NBitcoin.Protocol;

namespace NBitcoin.Tests
{
	[Trait("Altcoins", "Altcoins")]
	public class AltcoinTests
	{
		[Fact]
		public void CanParseDashBlock()
		{
			var b = Block.Parse("000000206d63d58759c321528ce5ef133da089ebe5060bd1f8e14463b0eed903140000004a530a73824ac9004b342f25dc2907ab46c045120018470f5460a7db5f79d7b5b3000667c462061ea51a05000503000500010000000000000000000000000000000000000000000000000000000000000000ffffffff06038a0e110101ffffffff03aa681d04000000001976a914c69a0bda7daaae481be8def95e5f347a1d00a4b488ac8815a10400000000016a7324b707000000001976a91464f2b2b84f62d68a2cd7f7f5fb2b5aa75ef716d788ac00000000af03008a0e1100ec97f6c7c4114354a92840644cfb3db02b41ee725eb4dd91a4e02089e87ca9734653abaf225714f4f3ee2f4534b755c647950da1cdeb862dbc1e5a54eacb872c00a8cf7096926c61cbca71a11b150e951ba616991a13f673d0a2a02cfaa753b2cf833b4632ec4f2e723ba3105e144b119a0adabaf96608f6bd535996ce246386e24320d4c1608d62f3ad8767f5aee90caaf82f08bb7d18c9bdb1a956a482c4d6a2ceb27b95f903000003000600000000000000fd490101008a0e1100030001fd79add882c919350988a278db3db1b1d447bff61b9315d4eaa0aae34601000032000000000000003200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000600000000000000fd550101008a0e1100030004fd79add882c919350988a278db3db1b1d447bff61b9315d4eaa0aae34601000064000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000600000000000000fd430101008a0e1100030006fd79add882c919350988a278db3db1b1d447bff61b9315d4eaa0aae34601000019000000001900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000900000180f0fa02000000001976a914a053585e3221877c0619468cf7abe3b8c69fd70988ac0000000091010000000000000000be000000890e1100d3c2de649a67063e0b5c9d8d7199059a320e84163563cbd00d23810833000000a26abc32997e8c8278f858bb553849a2db97679d33546822c5f2f566f4dd28bf39e0ada03d7840a324042a1e802797220c11199cdb3c96f69d051bf96d5d23d88cc9786cf7140240f8dc06b382b322906903777907b4d0680607b2a87cc6ae7d",
			Altcoins.AltNetworkSets.Dash.Testnet);
			Assert.Contains(b.Transactions, b => b.GetHash().ToString() == "6f0151cae1d5e65305fe2620a4f387108b054874a4ed9a23d4b004d5b08fbf79");
		}
		[Fact]
		public void NoCrashQuickTest()
		{
			HashSet<string> coins = new HashSet<string>();
			foreach (var network in NBitcoin.Altcoins.AltNetworkSets.GetAll().ToList())
			{
				if (network == Altcoins.AltNetworkSets.Liquid) // No testnet
					continue;
				Assert.True(coins.Add(network.CryptoCode.ToLowerInvariant()));
				Assert.NotEqual(network.Mainnet, network.Regtest);
				Assert.NotEqual(network.Regtest, network.Testnet);
				Assert.Equal(network.Regtest.NetworkSet, network.Testnet.NetworkSet);
				Assert.Equal(network.Mainnet.NetworkSet, network.Testnet.NetworkSet);
				Assert.Equal(network, network.Testnet.NetworkSet);
				Assert.Equal(ChainName.Mainnet, network.Mainnet.ChainName);
				Assert.Equal(ChainName.Testnet, network.Testnet.ChainName);
				Assert.Equal(ChainName.Regtest, network.Regtest.ChainName);
				Assert.Equal(network.CryptoCode, network.CryptoCode.ToUpperInvariant());
				Assert.Equal(network.Mainnet, Network.GetNetwork(network.CryptoCode.ToLowerInvariant() + "-mainnet"));
				Assert.Equal(network.Testnet, Network.GetNetwork(network.CryptoCode.ToLowerInvariant() + "-testnet"));
				Assert.Equal(network.Regtest, Network.GetNetwork(network.CryptoCode.ToLowerInvariant() + "-regtest"));

				foreach (var n in new[] { network.Mainnet, network.Testnet, network.Regtest })
				{
					n.Parse(new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, n).ToString());
				}
			}
		}


		[Fact]
		public async Task CanCalculateTransactionHash()
		{
			using (var builder = NodeBuilderEx.Create())
			{
				var rpc = builder.CreateNode().CreateRPCClient();
				builder.StartAll();
				var blockHash = (await rpc.GenerateAsync(10))[0];
				var block = rpc.GetBlock(blockHash);

				Transaction walletTx = null;
				try
				{
					walletTx = rpc.GetRawTransaction(block.Transactions[0].GetHash(), block.GetHash());
				}
				// Some nodes does not support the blockid
				catch
				{
					walletTx = rpc.GetRawTransaction(block.Transactions[0].GetHash());
				}
				Assert.Equal(walletTx.ToHex(), block.Transactions[0].ToHex());
			}
		}

		[Fact]
		public void HasCorrectGenesisBlock()
		{
			using (var builder = NodeBuilderEx.Create())
			{
				var rpc = builder.CreateNode().CreateRPCClient();
				builder.StartAll();
				var genesis = rpc.GetBlock(0);
				if (IsElements(builder.Network))
				{
					Assert.Contains(genesis.Transactions.SelectMany(t => t.Outputs).OfType<ElementsTxOut>(), o => o.IsPeggedAsset == true && o.ConfidentialValue.Amount != null && o.ConfidentialValue.Amount != Money.Zero);
				}
				var actual = genesis.GetHash();
				var calculatedGenesis = builder.Network.GetGenesis().GetHash();
				Assert.Equal(calculatedGenesis, actual);
				Assert.Equal(rpc.GetBlockHash(0), calculatedGenesis);
			}
		}

		[Fact]
		public async Task CanParseBlock()
		{
			using (var builder = NodeBuilderEx.Create())
			{

				var node = builder.CreateNode();
				builder.StartAll();
				var rpc = node.CreateRPCClient();
				await rpc.GenerateAsync(10);
				var hash = await rpc.GetBestBlockHashAsync();
				var b = await rpc.GetBlockAsync(hash);
				Assert.NotNull(b);
				Assert.Equal(hash, b.GetHash());

				new ConcurrentChain(builder.Network);
			}
		}
		[Theory]
		[Trait("UnitTest", "UnitTest")]
		[InlineData(1234)]
		[InlineData(0.9)]
		public void CanSerializeDeserializeFeeFilter(decimal satPerBytes)
		{
			var p = new FeeFilterPayload();
			p.FeeRate = new FeeRate(satPerBytes);
			var bytes = p.ToBytes();
			p = new FeeFilterPayload();
			p.ReadWrite(new BitcoinStream(bytes));
			Assert.Equal(new FeeRate(satPerBytes), p.FeeRate);
		}
		[Fact]
		[Trait("UnitTest", "UnitTest")]
		public void ElementsAddressSerializationTest()
		{

			var network = Altcoins.Liquid.Instance.Regtest;
			var address =
				"el1qqvx2mprx8re8pd7xjeg9tu8w3jllhcty05l0hlyvlsaj0rce90nk97ze47dv3sy356nuxhjlpms73ztf8lalkerz9ndvg0rva";
			var bitcoinBlindedAddress = new BitcoinBlindedAddress(address, network);
			var seria = new JsonSerializerSettings();
			Serializer.RegisterFrontConverters(seria, network);
			var serializer = JsonSerializer.Create(seria);
			using (var textWriter = new StringWriter())
			{
				serializer.Serialize(textWriter, bitcoinBlindedAddress);

				Assert.Equal(address, textWriter.ToString().Trim('"'));

				using (var textReader = new JsonTextReader(new StringReader(textWriter.ToString())))
				{

					Assert.Equal(bitcoinBlindedAddress, serializer.Deserialize<BitcoinAddress>(textReader));
					Assert.Equal(bitcoinBlindedAddress, serializer.Deserialize<BitcoinBlindedAddress>(textReader));
					Assert.Throws<JsonObjectException>(() =>
					{
						Assert.Equal(bitcoinBlindedAddress, serializer.Deserialize<IDestination>(textReader));
					});
					Assert.Throws<JsonObjectException>(() =>
					{
						Assert.Equal(bitcoinBlindedAddress, serializer.Deserialize<IBitcoinString>(textReader));
					});
				}
			}

		}

		[Fact]
		public void Slip21Tests()
		{
			var allMnemonic = new Mnemonic("all all all all all all all all all all all all");
			var master = Slip21Node.FromSeed(allMnemonic.DeriveSeed());
			Assert.Equal("dbf12b44133eaab506a740f6565cc117228cbf1dd70635cfa8ddfdc9af734756", master.Key.ToHex());

			var child1 = master.DeriveChild("SLIP-0021");
			Assert.Equal("1d065e3ac1bbe5c7fad32cf2305f7d709dc070d672044a19e610c77cdf33de0d", child1.Key.ToHex());

			var child2 = child1.DeriveChild("Master encryption key");
			Assert.Equal("ea163130e35bbafdf5ddee97a17b39cef2be4b4f390180d65b54cf05c6a82fde", child2.Key.ToHex());

			var child3 = child1.DeriveChild("Authentication key");
			Assert.Equal("47194e938ab24cc82bfa25f6486ed54bebe79c40ae2a5a32ea6db294d81861a6", child3.Key.ToHex());
		}

		[Fact]
		public void Slip77Tests()
		{
			//test vector: https://github.com/vulpemventures/slip77/commit/bc0fa0c712512d27cf1e2d6e1aaee5c36a3d38fa
			var allMnemonic = new Mnemonic("all all all all all all all all all all all all");
			var master = Slip21Node.FromSeed(allMnemonic.DeriveSeed());
			Assert.Equal("dbf12b44133eaab506a740f6565cc117228cbf1dd70635cfa8ddfdc9af734756", master.Key.ToHex());
			var slip77 = Slip21Node.FromSeed(allMnemonic.DeriveSeed()).GetSlip77Node();
			var script = Script.FromHex("76a914a579388225827d9f2fe9014add644487808c695d88ac");
			var privateBlindingKey = slip77.DeriveSlip77BlindingKey(script);
			var unconfidentialAddress =
				BitcoinAddress.Create("2dpWh6jbhAowNsQ5agtFzi7j6nKscj6UnEr", Altcoins.Liquid.Instance.Regtest);
			var publicBlindingKey = privateBlindingKey.PubKey;
			Assert.Equal("4e6e94df28448c7bb159271fe546da464ea863b3887d2eec6afd841184b70592",
				privateBlindingKey.ToHex());
			Assert.Equal("0223ef5cf5d1185f86204b9386c8541061a24b6f72fa4a29e3a0b60e1c20ffaf5b",
				publicBlindingKey.ToHex());
			Assert.Equal("CTEkf75DFff5ReB7juTg2oehrj41aMj21kvvJaQdWsEAQohz1EDhu7Ayh6goxpz3GZRVKidTtaXaXYEJ",
				new BitcoinBlindedAddress(publicBlindingKey, unconfidentialAddress).ToString());


			//test vector: https://github.com/trezor/trezor-firmware/pull/398/files#diff-afc9a622fb2281269983493a9da47a364e94f8bd338e5322f4a7cef99f98ee69R119
			var abusiveWords =
				new Mnemonic("alcohol woman abuse must during monitor noble actual mixed trade anger aisle");
			var derived = abusiveWords.DeriveExtKey().Derive(new KeyPath("44'/1'/0'/0/0"));
			var addr = derived.PrivateKey.GetAddress(ScriptPubKeyType.Legacy, Liquid.Instance.Regtest);
			var abusiveslip77 = Slip21Node.FromSeed(abusiveWords.DeriveSeed()).GetSlip77Node();
			Assert.Equal("26f1dc2c52222394236d76e0809516255cfcca94069fd5187c0f090d18f42ad6",
				abusiveslip77.DeriveSlip77BlindingKey(addr.ScriptPubKey).ToHex());
		}

		[Fact]
		public void ElementsDynFedBlockTests()
		{
			var stream = File.ReadAllBytes(
				$"data/liquid-block-mainnet-4a9c3c0fbb1cb8ff106f474e967bc5ec17a7ba066abd605bfc6f0c078e79b609"); ;
			var networkM = Altcoins.Liquid.Instance.Mainnet;
			var h = "4a9c3c0fbb1cb8ff106f474e967bc5ec17a7ba066abd605bfc6f0c078e79b609";
			var b = networkM.Consensus.ConsensusFactory.CreateBlock();
			b.ReadWrite(stream, networkM.Consensus.ConsensusFactory);
			Assert.Equal(h, b.GetHash().ToString());

			var network = Altcoins.Liquid.Instance.Regtest;
			var dynFedBlockHash = "1d4cbd13031d7b0b140ae5e2b941c4a91a5715210cfee5a739125b8a437c157b";
			var dynFedBlock =
				"000000a001f95134430f3eacc4df49783a6089a54e6d8b4324426ae10aabde1fa700984cbc3709eacae2db87ccc9e93f6004822dd581b0180e610dffe3d74d266f632cc3a89e1b610e000000012200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc3326088050000fbee9cea00d8efdc49cfbec328537e0d7032194de6ebf3cf42e5c05bb89a08b100010151010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035e0101ffffffff0201336f29c87301854e72291060b1610b4de92e06f6f864c9309d52fdc7f158aa5801000000000000000000016a01336f29c87301854e72291060b1610b4de92e06f6f864c9309d52fdc7f158aa5801000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000";
			var block = Block.Parse(dynFedBlock, network);
			Assert.Equal(dynFedBlockHash, block.GetHash().ToString());

			var nonDynFedBlockHash = "403787c0b91bb0fdeacd3091f282cc0b44561855fa4ca9fd0f3697e8dccf410f";
			var nonDynFedBlock =
				"00000020a048b48d6be7b296edd0e0dc33a69940086107eb691df8961ec9ac39ad7f98ffcb9dab8e1ca3aef2c6ff17fae61907e3f45f669f184f440762c8cde2b1c270ce919e1b6105000000015100010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03550101ffffffff0201336f29c87301854e72291060b1610b4de92e06f6f864c9309d52fdc7f158aa5801000000000000000000016a01336f29c87301854e72291060b1610b4de92e06f6f864c9309d52fdc7f158aa5801000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000";
			block = Block.Parse(nonDynFedBlock, network);
			Assert.Equal(nonDynFedBlockHash, block.GetHash().ToString());
		}

		[Fact]
		public void ElementsAddressTests()
		{
			var network = Altcoins.Liquid.Instance.Mainnet;
			//p2sh-segwit blidned addresses mainnet
			var key = Key.Parse("L22adb3BwuUxLoE8jDhNS7y9e92AYaHpXH5HSXZtFUKdJddEuFgm", network);
			var blindingKey =
				new Key(Encoders.Hex.DecodeData("bb1cbb24decbf8510c0db6ced89a0fca20382ef0aba1fcffc0f70b4310320892"));
			var pubBlindingKey = new PubKey("0287bad01b847963609da945cd5a08a1937649f7adbfdba5dc7e9ff46a44e54bed");
			Assert.Equal(blindingKey.PubKey, pubBlindingKey);
			var p2sh = key.PubKey.GetAddress(ScriptPubKeyType.SegwitP2SH, network);
			Assert.Equal("GtqMkbR82hDis4EPKAaBNSKHY2ZR3ue4Ef", p2sh.ToString());
			var blinded = new BitcoinBlindedAddress("VJL9DzChzwuw7Amnb1SL7M5WEq4TXmzZeAWzNFM5ULcr84gUEpu46Hbs1hZoYJXVkaqM5E3YxAyHy18N", network);
			Assert.Equal(blinded.ToString(), new BitcoinBlindedAddress(pubBlindingKey, p2sh).ToString());

			//legacy blinded addresses mainnet
			key = Key.Parse("L2EApGmxCemfhVHRmgXa1TWRoEaoiKfJHfvfmcPpRzF2v6neHqZv", network);
			var legacy = key.PubKey.GetAddress(ScriptPubKeyType.Legacy, network);
			Assert.Equal("QCAGkwismL6CZ8LR1Bvbzx1z3S7dfNsPwv", legacy.ToString());
			blinded = new BitcoinBlindedAddress("VTpxFwLujc7Z8ufVaVxz1JJq7wVcmq6dxc4dDVBa9jK1zyHfuTUXQpZAhG4JJjv2DYGTKRW5r39RuHXF", network);
			Assert.Equal(blinded.ToString(), new BitcoinBlindedAddress(new PubKey("029c293fbb855b709d7af1b696f26b16de06de6746616ebee32aee07be9aadc5f0"), legacy).ToString());


			//segwit blinded addresses mainnet
			key = Key.Parse("KxYLpF8yCrphfji3AFzDFurjFfZum9wFhqzpQVeGAFRi4Gtewu6z", network);
			var segwit = key.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
			Assert.Equal("ex1qrqm9fah7lu9vf6t8v8tsjg0ul9gdtd89gwqcfz", segwit.ToString());
			blinded = new BitcoinBlindedAddress("lq1qqds20c9qasz0y9fup3wc8xca6ceeyf7e4w6wd9l4qd4vwh887l76gxpk2nm0alc2cn5kwcwhpysle72s6k6w2uhhtgwsltf66", network);
			var computed =
				new BitcoinBlindedAddress(
					new PubKey("0360a7e0a0ec04f2153c0c5d839b1dd6339227d9abb4e697f5036ac75ce7f7fda4"), segwit);

			Assert.Equal(blinded.ToString(), computed.ToString());
		}

		[Fact]
		public void ElementsTxOutSerializationTests()
		{
			var h =
				"0a7b4f211a0b435ff617123b9c88bbb9ae4c30583ed596417c40eca4d6f55b9eff00030d9ad60af2c4163a7045ed80231120136b5d909ac9fd9795f507315dac5bdbb517a914fb8f4bd01ffd273d9554a794b512538e971304d887";
			var txout = Assert.IsType<ElementsTxOut<Liquid>>(Liquid.Instance.Mainnet.Consensus.ConsensusFactory
				.CreateTxOut());
			txout.FromBytes(Encoders.Hex.DecodeData(h));


			var tx =
				"020000000101a05b1925607e3db672edb202f5d9b81b930978c95960515739c9ed57a58bb8d00100000000fdffffff030bfaf2fd8f89a62ca8db03e024d7324668f97b1176277646abc394004f9d562873084100bee5b62ded2426790366eca0126709ee34bcc9ec398a8061cdebe50794d003e43df2198ed2ce5eba399c91009ff97c17e93df1ddbcd59ea90a8dc4f6c1850c160014a1995e7ecd42edbb55a42f16f1fed8db25dc27800ab583e50eb9ae2aa416be56cac0238600d5894f198421a94cec1f9cc7e5dd546a09c3d799315fffc30183c5b90484d2d9b7faec2f3d0a07da8cdbb11735f2e3a33203fdbd472732e25e8a61a76e81efa05fc99bf2549b168b016376ef8be57db36e9316001439da5d9ba1f537ed4642bc2d89955002465e64d201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b201000000000000137600006b000000000002473044022045130aff46e9593950eab4f44c6f96851492f38b2795197e5cc038ad44cd76a9022021172099741cdf2f0849bfeb1cebf485b10e4f88b369d472933ee0c95f2e77fc012102732e9d72d6d7fa3eb023206c35c0445433e226bdc973d6dd7771919a0cdcc44d004301000157f0edfeca4d073093b87a9780c67e94a49e310292b0811576d8474f85c65afff1d91a81b59a5d5209c9c22ae8f89d0b6185ce538e23176a2ccd78e50834ba80fd4e1060330000000000000001aedd8801b3ec3e3578eb0ce0345fffb4c216b18cb041473d8cfca61627e74e76a6829de13287687e54704b2f742acc1c3716f31c3a34c6ece457a85302f7249795eefa8f6058b9a4542ac44d47a484577ba7b5719f3880418894aade9940512f8886e603497e2e54da1eed6f0e5df32974233658a0d75f028f108ec27d82172eb2317d188b3ae80d1264aac7824e939d692824f539ac951f7c456a8f87b577617db9abac1a77aff2755b447c4de4ffdd92a0eea1b8801f9325c89b676dbfd9462687fc1acacdb865c5b0e1a6927e6cb098f73e29501494ae0a4e2e89816cb974bde9cfda84f6280e9abe05f01432c0e87189349ee204af260c4c0e9f4267a0d3fed07f60e38037e4fd663d5c0355ea54ef4bcd08d4e947fc32b4fadefe1a7a8bc79c348088d8694b6389ee0a02e9551ff57520bda14ddc14bc7fcb5470ed3670431daf3c39f8dde06fb1f2eb1833746cb787be706196aa772a0dd31550da42760d59d936cd8aed6b8dc48dfc893a7d20d47e4bf6369234623878d7de29562dd23356bc66212f2f6e87264f731a7791d354453a728c61b5dfe5ff60c345aac918bc8eae8c6392cbfcf325277761fe1efa56fd4c82e9458b41860dd0a7dba8e3f4d2c321483425809ec6914173e84c45a166c795f49e5215b3c25c8e56706944b0786d25eb478e969a27d71c5639c376b51ac3dda5134ca278fd13c5663a7787da3833d28c0f3f75bde2830930ade7aac4f16e87962ed9592e8e4b270ce6a036d3e01bbe312896b8c9ed96cf733fc121f71d523e798d596467293a5e4fa0ea12d93ebc950e0346ef2c93891a9c6edb708dcced1a9c8d849ffbf9d992b552761560b180bb839bcdcacffcf815e6b41b091367b3593ee5881ebfd3049d7e4f7a201e0f2cf0ad739da2ba64b8f41efe86b5a54553516c84a95ca64b6568b5542442344d6abf09bad5f45aa392b3834d046e689264dc888a55bdecfbe2d4ce039b0544bd4bd438fbb3f4fd95d366775ed188f3c2cab6efc0335b59d5bae01d20f8ec211fee80f3d435244909b75ebba20c4897aaa84f6fdca40843de5443ae0ee6c1f5c16ef12f7e0050f42390c319d6b813c16a2e240f0ba96773f49a26b24fc68c488c560fc023f24ad78e2bd5d0bd247a63f8a213056d60f03ce38dccf63e0bacbd8ce70ef7198daaa2464cea9e2fb188b0111bc4cb6e7a0fe2d982bd512befb20a7bce961012cd1f761f2eb2ff8133f15f5596b00a7f44768b3a192527a91fc0ef5f50fa3366a34e4d58fe9bf73f2710791921843d620303b5d1fa0b1bf29a44cc5fc73c78528e5b3a409113dcabf0d190ac21b51a275859ea92aae2e44173407b4b23d1500469056f844c23057dae5e29c452b885a6cc03c6c82e7d4e36ed9e752ed1fe30926a09e054a022d075e8315c2ee77f761aca52278a1c18f874a77b109b9e955dc68f846873f61c7ec4fc1078bc9567ffefb45708ec4dc22b8918f5afd6c4ea5487166fa4e7f20f70a1d5be0e4d700affd4cb1a95df8e81294e43033c8017a7afe3942cac62303e919e19d1c455bfea36abd7cdc7701df348a5ec512736654082d6dc77281b32b70a3d7c01078790980ba47ec21b62a5eb0b60939a812b49662661e5108de6bd26c6320b043f31184d46870ebd53ec97b5e25269a87058a3b863a532b0ec2e71bceb8ec2315b11071bdbedfd8085d7896348715f750c962bb875be53996bf3237c5de84e26a02db7c557bee5dfc996a13e853091d3ad31c448d82fbced6c1b9d7f77d7918215c67413d654ad37354a0eebb9a3373130e896653da9f0d2208becc07e88c018dab6faf95a3371ddeb50b11b7a59ce90334fdeaef70ff29bde0b853a7ad7e2567348b24e2d89576ed7d49792c29be0ad3af1cf89c4af10b3f0d2aa8ac637705aa6c6ae287c11032dac094b3d48b6247bfe5bafb9019cdcd0871ec16265d6c4c037ce37b36f0cc5fd02c6d03be1b5efe4d5057d96c99cccadee38cb0a0562206a7474909b29875ea620fa88df0d935cf09f3f62667109d7bf6bb82e13274ca3308b5b878d8493d97a791de84a124bd084d9907f8ea5f6f85e3166a95ed51424911c487b95f5fbae46c3be8a34b30adcb3f097a871cb3b49e53b1f88a33b2ef91deff1ee1043c28b9c8da86c58bd9e87b86f28460181c9817dafb7971386a8dd87e06124d8569a5ab4ff35096861b5781282010cc3b1d949d8c6544842155a17165b8bcef6ca0b89a262e69398953ae1af6dddbca231fd8388474b672fb135c1943ac6626d69591be5a8feccffcc3ae596d71f4814fc29f24d997412196f4c824a529fcf83ea94b0b73ef2f168d578db6be898dc0124c6e0be9be57e836066d6ed1d47beba3668b32c95166fbec3a288a6ddb62a432b2aae55769d884d42bdea1fd06029dd6d81a1c92cf9d68cf31acdb25f2af12980433f437ee1fa7c4044ba1d1131afe0958e22cc44fa41c9b8b1a80c8539b3c6f16ab8eb5f0932cbed59fae61b8f491eac5d2284b4da3608e41a056000e7f413adfebb000acc5102728996da400fb4297ba433a1389adba9ede7ba9926ae81f28fd7c1d1f225b4b768a5a404f9051d00f94bf80930bc9d7598b0bd8b8453eb2271bdc67ababe9a61c25af580c64c2c12779718a4b124de10206d6cb77a67ad225d5f377d988a3e45f5d5df57d963703c8f89fbb1539435ab17607411900a4997c07ff435863e5076a115174a24c51a07ece318c930639bb154c4346c99c7b011053fe7375fe00186a2be95f588d342df11c2f4821bebbf47c82adb19838382c1abc4c1b2d45dc76202dde93dff4b6f7d1e652a642c0d176bf5359a80a33bdf942077f1d38fc73c8e8b2353c068580c1acd4f818510b9abbfbca036b25d891d2fea6f490d2246663fc9e180d5bd20272e7d22250f5583ffda581734168eea8fb690a25cfa4a0666fe4a4b3043541bd71dac277ff5671a3da9115aea3280029cb74b5bed2ee002da494ac62513919f8845a9e3adee00134b2d7a829dbc26703f2c146c86995177c6fe3af0dc26143d5b49e9660b67ff3a8a9cf2846b4f10aca8490d91f445995a694b5290d8c5bafe07d50e0c93a1254099302ad8a61800fad317cf9459ea66ca88fda3f604e3180d8cb32e9e6e0b0168d89c3ea43f0f4a86d5ac875198aa62abae2b90a91975d930d337f0d457f69c1cfabbc4cb92770b9ee33bda8ba6dc3a25d04668af606b01262d9a5497ca583052eb7d497463a768836bc57d76fab733e329d373b403b4c9838982dd2ad6e73775b83ea82847bc10314a4d0d826977995dbd94bb37ecaf38f8cabad6d68c4e8930319b8f61b1d299c2bb5516d6cada78d99982e70806a8a3e8bccca31f976709796da5ce796fef082b7b2df834c8b69525972ae2b34e292e40cba19988bbe6ca85a8505f56008d0dec8838e90d9c112ab37251734427410fbe0c6e447036139b113b35a42023c39ac7adc04463bb106204c6236d4ea9831b71adb45a5a2d41376dd737ae32a09a98a174637137294605baed33333afae9a4da242a9c2ec6f3e6f72e43a62aacba377976637f8ce1695d7fa7b70a30a32c2e59da1073ab085852320ec52eb93d1ca7379f397b0eb27c19f66d9f07581af96a497c174449b1e55ac287533d560fc6af72037c473cef6e8ceef544835c24aeb4734b5dcc8475a15cc78a6f60fd7131ccd96bd99761f191dd509ad484723131346b785fff13967271f1af39f11730b3f74559285aea19f8d624a8803e720c7ec227ad25d6a75fd74d0cff9ae2c8b1330cfbaa4067597a848a03e8d44c98ed2a5aed8859ecea63d7447c01f1b54ef444dc1596bea2e757dc915a94d162c8a9b38cd72bc500409d50802b2ca4d20695179f1a6cfad761ab6594a7048cf4ef4907c5df4463f57b8071c7c83fdaa45ad50fe83b6c3f68a82729b84261f3649d6b9b46e2f0df4c50a3022350b39aad5063af4123ea7a385266d434a2d1533afce50dcb78ddc70b3cc8eab6e7e3f1dc8ef8713fb3a200e4ef2b9363a8d43c5730928737b218bfb8d969e5cd601b44c319bb6c27ace6d718f7555bef8aca7058aa6b7144556c396d87ba47cce1ccc2e78997f0147c56ba4b2e118febf6e1c73ab1b09c62179bd9a73788cc24a70a3fd1c7066cb3c2f6518f7ee1cda6b628a0fb63487dc1500e7d604b1b596dfeaaaa907b87be679d1c6ffb01ed93d966b1690649ea7928499ea6a876c862be81390092ffe37164b56f6a944725345f5fbc2bbc175263e95885d2dc3c84205bcfb44011a938e929252000f70bf295a0b19d45347088374a066bac9ad8ca02cdff2b369367644f98c8b0327fa8890064e91375930375026dcd52f4c4153794315ea6d5be3632b3cf79098e271b4b8be3dd4be4cd7407dd0fa7705cecba076a25f6a37702cdf20c4389eae2c4302f3a6d0a0ae2f4158fad2ba939739c34d4ab7438e1447e9a3109c59a2809eb3e99b61bc31527a578dae8d7404404b05d17c5cb6f3d9de03e0bc0604539d6abe2a35c77d696e9fec05332f3f42a0ec38478841e3dba65edfda13344b1777ee02c121924fdf35e486531e4a25fbe9832d9fe1c3443b17034901d04e9e6339a717d30c0a9e31659b977a01ba126064713a976d00effd4e193e956d6a754fdec59570cb01fd8d86a6ba36326a9dd8c079bfdf4327d545ce19604ad3f7039f0128fa5a9f22a89e394224935c4d27b6799c15b995d9462dd3d262a69bb49e928c7ccc2d5117f81b1bace5ef2f0cdf452c8e510142f7a7babf6f450a5fd39990b32b4e8ea0d05e1e7c7bdcaa0b53c2f89a150c2305fea115c739049bd53bea182be73b775be314c99da163460a4cb34c0b9beef62c5bc204bcbd88de4a8e27bcce49e8bcca547549e6ca86476fbb40e05fd610e7a64cb128b293eeebac1687fadda9bfa51a6b01d2db5cf580f59f249a8cb6fec88b2e575627005293d3da11e03c2927f33aa64872ef22186c7bc767d88b99863a7d0862143f760efaf598d402ad8a8b64699f136bfd02f128761376f2a209747609962b84a9643656f55f9067b98025f45286c4ab92c0e5e6fc2eb7093563856ee05674e0a3a43fb7f40f70804fb4201af1b61badfc44e0cb99e1cc3469bfc8016bcf28ecd144b30cc0fe950dd3b32e67b1efe8f3ff95df3a909e1e4e939d1ee5d219e625a2ee068a3850ca9098c832f9111766a26e6b9a152616da5084a9aa4d003b6d1a94fbcf382e1bd5453ada98a2e3c6d0ecdc3666d597a1565e3914754458954542bf6ed988ffc7164683bc26253d35290d19b30a05787162c79e823b42bb381aa2b59eaa4472749f7acdced81c474e19d23a64adc62353e27c4be17a898bbcaf57823b8d9078b980163741aab174890e623a95b7769fda949cd18492a9297b80dbc5be98fb9ae2d080db67979780139b58b4407b3ef47fb0d153eff797b155ca1b09242a27a1bb0a70f9d02112aef20e92a040b201351002fdd2b9700dff6f6f768b6ced628fec4b481e585e8de6dd7903125a379e70231b3d31a5900171ab4477fe8988b8fca89ab5e1c81483d4839f45a181a15034f950be60bc5392fdfb86283242eece416b7dc9f38b3e16aa31996732f4f8067e24f997ecdb0b87a2298b798385efbff0d8c63a49ac9180148ba70ad09a871079707c850d0d2ddfdeec9774f5a41b99e829132ea711e330808b6467b62342b4688448bf648b17f30063baeb141d7bb8c1d39343e1f1d43aa3537cadbc0a0348f8c2173449073ee3737598beff4e1fb4e6cbce89cd3b308fd8d7740632a5a2592f743210e152fc39616745b264749fd4b05eadc59a978b6c19a952ca91f1c7f03f923ed05afd91598a3c5fdf3a0ba82384d80c51fe2a1430100013b6bd464044fe5c098bb8b81e3be72ee46ef462e3f7e8ddc2316244626fd59dfff1c679d58156e9fa3f1518829fa41e88fe251cb362eefccfe5511a29e1126f1fd4e106033000000000000000156f18f01071ba30a833e2c10d9968f7820264ac6ad349aa769f9bd1d51ed19710362f819f728d3544d88ea9395cda2d82fe56fd0098e630f5e9d51f9ce937073b03c62ef9512a23a1ec9592ee4bc517976a042f3515afe94eca5c6f64df683fbca0d84411e5b36b672f190da193993429db1a3930d468787c4ae8888ddfb4e7821971359df16e83531f375f3d3cd931ee5a810b9f98d2ba5f3ef496888848fdcd314b27546e81eaa440802903cd38087bc90d1348a195528c9b9753d46933a69edbdd2cad851bdb97b782075beaa662075bd4f02294cfb55491fdd47853057958ef852f432803529e67f224e3f0b930a5843667816240c39ef403f9f0f47054099f938365cc4916f5633157c59550878d5332d6a267b1171b1fba8aa23ba1b87d9153b40c170499d77f04c86e7b896bb9d727908e40657c313ec3cbe0a9b36f30ddb6ab7d83413a68849b32328b49081c06b6077bf60f03f539588d6557309c2761efea96ecfef4a2c9d026b7a587f910fd30ac6329210bc25f72435a25c2ef7e31466b11faf657e1a3c279d05ef3b3832b4b36e346900a1d5f8be2fbda7f429dc863d42130ca18d87ec4ec9b20853a3778ed64bcf07fc7647bc27ec932059d76790571ab143b0b8063be3bdb07bcee8b7954ba1c6b77a076c0beef51e874cf213deaf9050ca6d6228d7ab3cb449d0e409097ec647df9386f052c90fce430e5d383ba85e9a84676709cce51796267d1ea7969cf370e7ae35f1303da9a9807fb92501de07c31d7390e69521d2fca89515de42b8bd3fb85b259b61e42ce888876fdd7a36d3ee5a204565ecbee8c80a4e4f2630f7c7a6beed738fa8c9a001c79d1907b8998404859923e3010d542973e6dfb9d0bca7922900df94fca082655d58e7552a47c02d67728185f2c5ec187a012b97993bbdce1f7a6f264314ac6dab6038f546d2f75cd4cc0e05962a249d7b1272a5b9ebd9e41451e90a73fba87fdaf403412bd3755ece001c8c5318a45105ae101837fe912e6ddadd12cf184eabf8e2ca574c761512e36ef3dd015a2798434e34b10d81575c0736fce322a7c63f437d422938bab09d27434a4ce9a3127af47ee2723775f768cc8ad61d8507e5dcf015e4ecd0de2c08212d840bb7e904f0c9081aca607345cf63c5edd9aa27d1402091bcb3da0e45734b991aef5d01382084ebc58031b3ae5bbb23459edf25be1d409112789ccee6e20b30a85654d481d3377324f2e7568e0300c7a654698283aefbf5f216330a46c073253aabefe2d928178fe05b732f595e8b55baed63a1c21384971ebaecd01c95fa5a0cfb622929b3b3fac49deee244552e1cc7c96c25b5d702e5b719ebbdf395af9b2b20b0f1a107db503c2313786ee3d95cff666bd78b08a012bca24efab7a32d8487fe56aad4f9f961c2a8fc08d91556be31244d535f48c6506bee5bbe3eaa30c79e67bd1c110834e0f54ab37a83534e80b26a3a9a192dcddd2b2fe7eaa9cdc3d25e17cf09d745d2cd7aa7c6a18959c457dbd871592d52f5bc97f7c8f1fc924396760591e621ba9165c23b67fa0f3096ea404526f6342c1662239311403fba06c3e32a676e780ea69fad008a2d73c388734ad11b732669240237e9eab89d3b2b9b29f34afe3729744d04654f308f053616fd5e0d4f846025e10202bc2e079bbc6049b590c1e955e25c02880663abf8fe0cf5283146134aede7db13e50d36a3734149702d5d2088bb1176b7a429ea700cfde5e5b95213d810873af80f92c4b13838a68a46be3b0a040298f4fa1a375ee8852f047eecfc67f2429f5049bb105e0d16c4fece636725170c716b893c964e8f8b4e9a1032a34959e95b2069d486001f7885b65f4e5c7f527551f959188516dda4becfbae8ea27eeb23c9409b71ac1973ccbe6a51733f1ebe7dabf636ac1fe03b5dd71fe120968d54eacbb10fc7fd9db020b11c7746c82903ea6595bde121f282478661e7004436a6d8f64e3864a66ff9129aadb838cc1da686b8768a11153fad6f450472feb90f275c9ef5e9eb0ffcdca444a452d621497a066ab016f8cc4ff1b8093061965b72b8601fb86513177a4ec2f415184ba1726d56c9cb5b92298e0cf95d623f684e804a225580187c0728ac01a6bd7efcf28df96773eb1ac35fc9c7d378461a595d7be2c8bcb6ecc394c3c28fa655b360860e61d6317ecfb9c3ed569a17676bd6b4100dc22642b9b8ab5f68897da3ba7897190b525b70b9067a4b7baa95622813db075b8bdb2b700094799763f01b7330e75be0c433b41a7b4c45f53c6be1e1048d0cc6ea3ff63d5a4053ed9154f090cd2962464abb12edb59ede8d76b3529ac49d7b0eeda122bb6fffefb1ba08f3f3e4ae10c2bdceb0ad3cab9bab908952c660b0dffd6501d85146d09af9b7ce577213361c2b5e1f285c222aba9f6b8fe490d2f71461d483a3315dc1602a273e4f3ded42da635ff2a50dec29335c7bb039a3598bba112d41b51f50dc3bbc4310ca6986ea0417345f743ff485ea979fb7f1737ee5347cb6fad18a54aeb142f5d2bfaa0cfda78fcbf84a6bcdb3b5e20ba00c6ee8ba019d60fc6b667b330f65afb8f3ebbeb6c2a6424c2cfc77f51f404a234fa03ded755d8b52b2cc4cc1570db4cb728b4e61a1d387697093912b28c2cc7af3bda7ce1bb46a8cf47635feda1b1598b411f6f6ec2e5bd3c66081a50007a22df0c056eb497873e05b1f9a58a7a9c16681015c913276b016d067972d304847afadad01840866d6abd142e283c22977a41fc58dd26dbdae8d4851f9aa40b0d58564fe15dac2a639634947b4a2377b87f1b357707462b9af34b6a12d634a68d8c40492c2b1c01d13fe30f75733c3064c2ec04cbb9119f531c541838368c2465c6771e005d652770751316735205f8d6303bc6acf151067608b7f2e16f6711f4b9c6759e6d464fbfbe42c8a6727f4d3deb21f5f6ef49e9079693f4d6658a1b49bb536afd2968e6002536e01326916d3689a2a2bc93b188ba3218fd7e53e393b393ecd686f68f5ad8a5bf82b19ee73fa6c5a69cb3ecbb90ab12c2818c63b5a5e82c7400e36570c37ba8e83d6fe39642537a6c55b9e2ea11bb10333cdd6bf35ddb67a674ff70a73e1e14487c83dfc4438523bbb344bdb4e9583126d8faa340e0d2aff6f008cda44491b693e724d535e96663a7fd640c88cc7b0c509721ce25a3750a9b7f3c7408cd936252c477ccee4878a03fb8a8e38eef261e82fa7a7819ec3e63e293fec557766cdc94817a9fd2b2ba1d8f65329a7d6bc068b7146510ab0710ed009f6167483b416158b709d0c049b765b6581601dcd0bcd55ef1efa373908c796a4a6696e1df2fd3517da328974d9b4b1dc0ef8235187d4fc1342e67177020c927317f844ee83dce017589ea5e7f7c0b5d246ca77c420cc353752d5321cab44068f7c5e5b475d9b51ccf03c17f3be579f4890da6ad3cda91edd0c01cf73899546f49b393acb2adb476a3a887e2f9611ff8d58014a532993922b4f030c1da8031503a17a312f95f724ab8071d53cfb3e421dbbc9783257c8a65fd9daf205f9688041d56a451483259c737052666631629c6f2671c8dd1101e8dac42cfe1e132c14c12709a78d6c9dc541b7a8b04db41d9f2d64a4e7d7e958269ed79124bafe32ddad9959da8d10104f7066f5a1bf4cf953cdb78fa5198b0e7fedbe3c86197a93c4bdac2716a02493deaea87d28a51dc98b3b60a0ff6382debb6c39bc51625f522835b08f9a3ebb192ce2778b95509dbb267d7e27092038a35641efb7ef8225b2159aa836006b8cf48e21bd4214398fcf840120443916c4f3e9b6d38f695e9d0cb0d893f59e09a2614de5742c533d24ce732066d1b374395e76c7e3e990f7c80340b9ffa142dfcd062222ea2096860f1d50bedf04ed6c20b61cbc32ece39a65401ccf2f3ffd742c2e9ab11f4e6e64287ce327a228edb34f12d54878568bcaa482adc81402dbc5ee04299050d2b02d8c99ae6f8b839cd702f253b9379ed4016ddb5cd90e1a01b6a7ddd2ff30f07d84b7e3e8b16a2c5b9195dbe704595ffe9bedeb30ba192526fcfcbe5bd5c40e77d391bdcb9cf69fcd5132b0129038e0eebcde5007113c0ec3503c3796380f2a267e02853c5ed67509d5e45316419cca67face35410f19e89eb5c9a98490e69f03d6ce0892da658f1ec2ef2013f5ad9e056bf458795922bd3d4e0c51f184a273327b72fbfc48fdca09d6cfea20c436505e2d5d0815ec573c06dbda380230382f203e442e1e63cfbaa1755c385df0bfa93d2f8ffb7a20d8d0b407b6cd10d1ac5002b101ba6a07976dfaab1b91b7ef857df43b20cd97205cae45cac7f1153f6467bb00de111d5078d385ccbb64657018c73002388fe0449271bf1884b2525a9816a8005d2a27fd19a34927b1da530309bf127a6d84e20a1016a3802af02f0db45468a3f8569f4cbb87eba8a2ef3e259aec07c3c52189a22b824216fd9cf6307a639c1e5765becbbd5edf99bce0f12e939617daf9a87413add1b94ed565e313981e471a2358b7f63b32b1748c89164a6b2ea89cfea925f0136a0544c7ece557cc232754f8d3713a7437abafbf07bbc40893ba0a9b43223125170c8db8e84d3f2dc1b8972a123ada900caba0dbd9aac5891d98995f514f5533ee59bf0c0ac7588d1d6aa9550406fc6b95e7fb178ad1b28d1cdf44807e11d2218a09b6150976a2a131b4fd8b9d3f0366a475ec472aaac0da6b00fa517306792254b6ab9ab9ca90e9534e4f7795eb559a2d57fa57cc9210ff5ac4bae304f81d3a8bbd0584268138cc27bdd01d8a0ae945d3aa0274c5fe80ba4b255ed1d092df2ce1b585509426546527107b822d196c2e0f3c631bca9f01d7dc8865bfc5c2c6e7ecd4d9b178103313151cb2596397a937d43b56b7d5d4b18d34140c71a9896132af6232585fe513726c3d6ed49af01b9a396f28286e6bbd72a9305aded157e7cc8000fc63c12edd8ad190971a0887ec3b7ecf2777ebe7d05bcefb295e2df46e23d2b3d1df4cbfa9a9e6a01007be13ff740a09cef74c7a96ebf4b2ad0fe3e49ddc8de3415eea73bd7b98499c17476fdc2e0c40d4ed9fd316806330a5657452230af16c9693910c74b1b7bcc22a2938f821d857fb83141c55c3aa9c85adf2b2421fa19c839cb564e6f0fb3029f8723acdebdac5d6eb07167262fd921703310b0c0155935278e538892fd269877ce7b17bff1ba67acb657a5869643e602c5e0640827fbe388fe9a5bb55ad36ce8d7afde319493b5cec6a4554d7cec970e6870883412655a00822a4e654f0bacd24e2e8aa57ec4025e1ed04772d42bdbc28397090e55074951dc5eda25e61b6f3180730bdfb767b9258281f77e2673ddbe3af254c7e91ef97c7561aa119b5e0113cd2a44705ba2902659afe05dd63417adfcccda3fda0978c205dc38082b05b806f48dbf980f8eb5dd5c8a1b7a95099c832c5ce029ba85e47bee9ab624f2739d617d570a1912c0c6e9a4c5bce66ef1fca5bcbfec8979cc6e674107b1b7b88dcee1e378f3dcc5a353fa6767c987bc6d8abf845865cb2b516f6cd648e329bd20e3b8f178bd22b584e90a5bc84f3e6f9e11e67715940f3a022fec1e3ef46334792518868a28561483123f1152614027a5777e1f03e7cb13cee58f775674242c84bd4fdace892519e879448c808933949d608c367d62ea14c7e21555f47ceb99317a3f9137fc95fe8ac52773c1840eabf616bf240c98b567885e9030cb4d1f2a6a4b8315de2dcd7d57c1e222f9abb03083be23dcd59f2cb1bc351d4499913b67347aa9d2fd52eb27b210b01ffb8cfcf8be5a42d9e7ec443d8f23cab3fae501a6d72519b5c4b3cd5484877abaa1d2234720594633943fb706cc537fce668ae2d5f80000";
			var expectedTxHash = "5ff73e926f206d95bea060ba66a9d696b734d98044754fb2141b157ae249500a";
			var parsedTx = Transaction.Parse(tx, NBitcoin.Altcoins.Liquid.Instance.Regtest);
			Assert.Equal(expectedTxHash, parsedTx.GetHash().ToString());
			parsedTx.PrecomputeHash(true, false);
			Assert.Equal(expectedTxHash, parsedTx.GetHash().ToString());

		}
		[Theory]
		[InlineData("4387ee875f9ae67144b361034151aa2d900635687e4007b22917695bb5d5640b",
			"020000000101ea50c91e4aef22cbffd2d92f064179ee83677ece4d386f0f943d53da30cc1a970100000000fdffffff030ae01e25f273d5c87d8455f630e1cd04c7a770cb956256e1982bc47608979c7d27099937773758d2751093726f1410710d1c4862c8f2cd5ed2d4767e70e1d03df7ae02183e6c158d0ca633fbf9599203b6e687938fdf62f9af9e1cfd2d14fe49d37dfd160014c13c2a21528136c258681669abeaa5072af0d2240be46b29cee40c806611bc22dbc2ad79d6c4b84883cf51378d866e1d5fcf3d8896098a81de0d0643f58256dc8031cba3d7a7792f8e49dd17218ae2f61ae6e8281e2102c5c02d597a481c00e1649d3c6070e078450a370422bc0306575aa29f4c90828716001499607eb3b59cb5c5ae6013e36bb41dac625efda501230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b201000000000000137600006b00000000000247304402200850bc315128f2c5a668c21c5b5f56061a43dcfbc315debec103dddb68d6abc60220118c9dc04f1fff53767d67a5252e483ceb4292831a5953b2ac15b18d5c9a91b9012103b731fe53e189c7a362bfdc294627fc736648a191bdab395d0c57f9546892bd140043010001e84fdb64bbda93fd7975a124f63fb73358f25802e014b72211ff80feda70a9ebb4e42f8c5e442e4d2450c83370afdd380d7a58aee795de097b8e5884dc9102c9fd4e1060330000000000000001daf70101c4b4d5265cb959ae0fc48608506a88939297a27ada1ccbd6cc7573253705cc7e2a1dfed716ded1dd8ddbc6024bc8b7fa6567448072a17f1bb76bb96b3baa8df0c2900f8d742904ee74639922cb458b7773c2349ec1acb42b0f2b3b8281ea0ab698aced72f486db13a44da9c230f07c1662f800d9d9ab7169dabee21b80488e2825e4f916f5c8d74d822e6e1eb17775f16566f784ae73276ca6770f9e9ae3be5e4aca443c79aaa37ed858f4f5212f46603cf612ec75dc6fb2a8daffc66fd3ed564fc4ccb05812e71a3c9ef40068be18e69c2075b0977244ddd77ce1a523076fc950b9d03594d1b271ee98f3c8277161c1f937970beffd75f093bb2d222e3a1bce3789f2d54850c656cddd0e6d29b75e78cdc645521745cc279cf1748e3d7a8b723966d5ee8b86458dc02a71e9e160f36e5af015bcfe0dfd6119da69f9da8691b74ad9132261a288fd04a1e1fcbdae679aa598de838b7c1968bd36805c50d0beea3569e42ff13755fdf25bea8c15353adc9cf981e9bd271f1267e2afa5966c89bca88b79274ca6d0c4617823726e0c8dc99910e1c52cf79aaf3fff4afbb7df3f3833e8f2df49c13185f4d3e36dd540037fb94f623535da1e6fea1cf36328ca1afa4653194d4027d10f3f9bf35407bc93702606c3be5d4174bbc75622a7d011010c6e4721f3b61e46bf769cc6bac79287bfc88d2de292b1edc06ff800d099726a4475672b4b7ae288af708faf325cc710db820152d5ac0f06467113cf7728752236fe45ff14c7ff325e656c13b5cbdefb9a00d378556f9c5362f53f9b2ade7b45ee50a9558b9e0fa3a2e4289b94a57c76d0896ea6e38f6db03984b34c7898bdec85ff55f5da49534aa4df40d18748718987cbe594188038835212d68f7eb285a0ad08984cfa1af4a2dcebf9aff56c6a94e75db79d7302cc0f8b073af1613c87f7884249b573007eea188b8912e57286670ace4d6b2c71d61e2ddd20672db13ef4841aa564bbfa7e1322596548ea450e2e7c49bcd2757706a026a345e6a07043a2eca1d2c7424fc3c6b76cf3e910a9d83a8dcbfe1f93828bf1da5120faf154b525e0fcaf9e2a38a534aa550d05fe6e56aeaf9a0da355276524ff6ce485bb5f15232542c070cb5c19ff642fdbbb2e1b3887d740da447ea789284c219e6208cbf907447fa2730fd6e6f74ed8a2bbbd1ee00015c4485e415c218c52f6e3c7333af9735b12d2e767c7c357b0898e47e0b909a69d318f7201e3784ad3273bfcb26b7b3a0dc639eb9a30edeaf9a3b670506fc29c7d9dfdf167b3898d30d98c136f580dcf14e445224e75bfc7518715753d2c7e9c90dbec7fc5328961073ab541c35e8322754a1169a8ff0f7d014679bbfc118ce2969013629fe90c3b9e867210485f75bcbd4e8f5d6dd071648427d5fdd126e47343536e04b530f6a2a91af4967d7f65a86023b5449773ce9646d081d007c254808563903ad85892eaac0d2296f6c121b0181c4d353f96ffea479cf58585b485d54485a61f5f395de9323dc51b7ab3bb683f72530055ff94a756ca942f3e9845c315e643a7fbe94374324114e262af2e642fbd19d175696c88f1ada34078ae713807607d99b9547b759bf4e39cf19c727cb9759d0ba8bc73841ad5aff7cd23dcf11da0145eba69966797fc4159737905e6bea7f6de795cc5899be1929608c8a6a5d50735470ca270aa4cf594af1676fb82e1229d59f03fd2926e683549c0e2ddc2096a8fe2b01f69a104dffe72e039a43defc3d864cd87baf558875da1df15f81e46a61a618842aaebb3c1f2b27ae3a80896d8b93a1f187b5f811e224d703c5632be0ac8d4774a7a110b10c49c6a1402a25f926610ca47f6ba77e9be75455abd59dbd4b2635952ded7e9354af69a88d214b19dd05b51e6278c56434774a6f16fceeafd0b7bc577375af23a5940fec1dd1ed6521180320d33e33be01e2fa9e17e816f7831596385fd71893d99d5aaf8c7f1979d01a9c2b354237594a04edf08046f60e72376c48fa934b826e4d8cf917bb49e4f2df1bcac50b58fe5537a9a748b7197a35e66fbdaae31c9d4c251a78683e72e3247b5c9b2a4135489f24b3f09b765abf4807196013c8ac8ac86e3e4c6842162856211ded3d0b3fccc80909db3d8b7a7aa866f6bf92390352de99be731747a2509995b658ba29ce6aa18d5901872c01a41edbc63147e6eb2e0a5c3bdf7c6464ed3b1773c6f05fae64e265a9d0c767644ee004ccd28a553fd534593a6f38ec620fd97ec4129624c2f70a96771412291f04c0276ac07b2dbbba60ab8de0e5f1fe971fa38d6d69e97e8d413220e11d7b5ee9e498f8fa908fa63e7b69c570ad9454a7d1a5044520b51fbafbfaa6a0272159ff86bd6136cd3128b1b591d085c57f1c5d46fb95a96ace3532263bdeac28946d742c75912f8579b28bed59ab55942a2c35f9558490432cc327717de0cc89baea7e86e2dd150622af2a1f4a7f3bc6018e3d5943e3c8c91146632372416a5906b85fb4053eba26a570495c6940d1cf7408ac37aa7a4094b26271f922bbd9be21f3e40e09756c575ac70ba7491639037243fefca4ba84db6b8d66384704c9c0260585455b848611e771b59f12c745a293ddedf9964fb839d4499a84f2c2bdc6c32d1c8a5646413e163cb480dc90e9333e84e2ba8c96de14e09f2d830730482507274ab8227a12598c38de0a7578562270a5c87fbd22679136f9ee99bebfeccf15232768d56c705a89f1431a00ef2b1cd20849ac49706824d72b8e3823000f86189a8ddb1013489849c7a10c95f3f9763b7d0f15d9be5dc3e0f26ef63be46982b83e7e497de2dcf7537ca34576557215978220458f21916448f474053868fa7aab25fa12cdd3d5f1af73712c5952f12a39fef69150d3bde80c4ecb9b53f40aadd12124b927dd10f7cc1e5d279c236cf8ec861647c96b5825d5009b7ef136e9e53af98217c4c41749e4a49d3aab69355f947086b5d9bb93fa7eab02a65d703ac00cfa658e3e1d3c376e1571e3a67a24f5265f87519bd5ef84f89135f35256d2d9787ecf99ad5ee983489903fc900bf7705910700cdade914c818c327b6b374a9c3998db0a612588966b03a12d98f1b1252b14c573c459c3bd678044d2eaece9df547667df77abb56368dff6ccab74c385b54ed2db4e3bb1cfcf0bef1f72de0aa943c8540f5387e47e782342b9541fc17b700257bfd12c16fcf31e157ca9ef96414b1f2076a5812b76e75730df1128d9423b26d420685418a9b62874861c5353fd7d452428b3a1a57a246509f000c15dedf9814fd03e38e48d731621f76fc9676a75afb618349d500b54479c07bff6f7a9600d5221b05379ce1aefb0bd69f1ef290638ba7c95b643d90376fa1c65c8cf9e4b4e7658863646086f7757f3781cae53f7481dc4bedc33549e892d72ddd30e12cd74958835cc983efbf95b547c316cf0a37dbc36a4fa28c1ee96c14cbd43680a30cfa67551bb2f58f12b5087a5ee0a8769ae9bbc87dd01f9527b4b375f38e7f6211ab60f3496b28161e68d4feb74a81654a7eea270f9ce81ce54b01e5052f3dc07542a2db65c68f90764dbed923d739c52dd8503a0a42e690171786881592f3f5ed931a2682af98a838f81ffc52f35416e2b185bfd8c6318e810b651fbbd807c5c0d62b848df6cff5fdd48d004e8332f66c353c882babec74fd8869bf6b0352750f5f087471943b408fa60949e819a4ab706c3d9c4a1d9e196b6aa4e1b21a8db5019f76e7e6733bc385ce165362326bf6d84d54eb4cc505bf41819a634a44994e29ea163c0247076788fd853a7a0aa7612883114f1e59bccbd08d3099eb8b94c28904280ce6a08f6b96266122622016bad77d0418eab4bb2fe8dec1f474a8db2e3bd49ad7cbaf10f7bc1c42487ca5cd94715d360f836fa278ff6372e2ee6426220d0413b795a524410179913681dcc3db7381af72528bcb93305af47fb52b39375f7139c73ecf51125cda3ca16122ec583ac5277eee91956c7f1a808482a37903a93932a5d982dfb7f106c7104092d981fd9cbeb5d79292fa7b7bdbfab2dc4a21a9978ee2195ecca955442e37b902acb1e94e41d0bff0e0a669912a8c173b6152b06d8cf4c9f890ae16dfc19b6d9dfc23d7e209baae997dd3d0344477a56b3b3043e42576ae2bebecb07f6c6f7c995ae490336c425b531c926f8ec63c25fbcc5ce13e18bb0c3d531ff166c7f9aab01c33164246998e00b3db62ca52f7d77a5195958166d0253d19e57f2b3d0353e5dd36f2682f03c53cbb58a0d13044f162752aa94d61c6c7eb4a4909edd2cef603f134c895e704ab02f2226ab46bb7c1d92c804994769191f25e6f21e7027de7fb2614fb2de8969f06808633d555418f93c4da6ce55b942bf0a44b5ab1ed31d6512632bb22be1115aee6b3e0678a4dd4399d3e25761839e56a1a7db14392e4e12382cd1e249a7db0ac5c602290b19a0c3c75cc6e67f419471dfdf97a308bbe6a4b81bdde5a057dd8d7b0ef738ad307ea45521c65b90ce5147867ea26aa392208fe45215a7c823e1ae21fc8f39204550c9b8173dd53479cb67cd397a7fd615099b3f49d3ee678efebfa984228a5eb3544039331724a77ada1eb6f307445999d2fe492c73b78d3aa237b5d1f8c830bbc5b693467c8ca06a56e9fce76e2728cf47469617fb3216449e47313ff2bd043b3180168bd0157b1287c0675532ddb89bbe7051585b8de711ca19ef44b500191917961f146dad6fa8d977b1ad816d853940e604f623686ca733091c7e61cb79aa5ab5b73b6714143e4d13bd122776fd7fc28effa7412cc70399bf3df4d97ee72061bfededb33d7ddff7c7f6dd7f6e6b6cdb7d3a0ba6967f34d3175df8de41720c8e728af1fdba4493984f9b64abb185c49696e1ee6ee2fe9a178b1c683dfb4f389675087a54e977ae2bb321761c84c4ee0f75bb9ed7963420a6871a51f7e2f19474e7677df8dfe0b07ee67b2a4e96d72bcc32914635ed9e2af9f5fd69896babd29c467a199fde9d4a4db23a3b4040e04ac47562af0ace51a14151d2b792f42d1ef3bb723be356c2f30ec4fbdc3f71922495ea91ee06c0b2cb15c98cd11c25cfaede9b6059515429a48c9a06020adf21c59d7b7fcaed308aaa0c09e9aa8c4d55fe4bf864e900d972fc6b9751b5061af51aab23170404ad6afa23a9269a82ed5adb2d4213ea563f17fb327e2db64fafea8c38edd0c9c1e9ee65bcb58cb3e1ae481c97c020ad1aac0da7126ff2be3b0d72fc7471336fe50b71f1b3a3ac091aae5eda69bc3bacc7e8c425d2ad4f7b27c79961188bf80447601f67c08d0f4dbbe42e0926bb5c5d678d114cc803013ed23cbde2f12106cb206c2653173951b1799f85272b1e6ca58c780e7988c84abb54b50bb7044e35ed1054873ac6ea437427b7caea2e49ab22b4c7e476f4a31b5ae97192175e3bbd9bc229139313770ac5e84d9ec87b6af159f684529f6dabc06785ec6dd704a34557068e0df0a0fab28072e4275510a7b7eb8c2a0c192885236dd979ad6c3a292f104dfdd00d9728593182a6029c8109f4712048b7da089d42d1db9a6701e12aeaf7a1c27eb7aea61488c7b5138febbf55a570be4b561746975aee80af84c034ff49edf68c9c335f2c68ffb630e7bfe19d8ab6d1796ed1af80ecf20d2f24a8d77743debe13b5b1425fc92ff29387a9f2879031b57a91c16cffb57799775444aa9c48714f8c9498459d36391251abc14ced601a16f327c013131a261d995e15b5d2ba203121aa1c1e6ca2d6c1ba23b1a195be5d3fdbadff1d361249f2101cf4828f84c702353655f7c140f0310550ea19c88ac966aec75cbed9a46cbb61faa947b9b7dfaec2e3a01e5eee1caf70f188d8b03afb28f43010001ee84a49f515331ff66aad9a58a2fdc303a1187139df8e5e76459468b3f2b88cb4b51da93409e9c80dfdb62983dee748343e7ac765e483564bda00ce1965530dbfd4e106033000000000000000162d91b01cc67165f69e1b72eda50a47af6624481a557a814ad8d3bf423dd31f033466b98dd21d8d228bda163b7350523947530a6f02ba67066148f51be7b9d86144ffa045378c2052aa5dd28fd5c45134f34860b15a7a09cf4147bc27f5529af650a9fcb69c77f121bf82a1437902226a6a666c66429446a2b5266e7870447f215f2b7a2c6a4da776775eed3cc33019a2fd0a4b6ad52557252cbebe63b536c6ebdb52d947e70507bce9839260124d64410b9f601c477deb95dfc87026a2518d65d2e158698cd9129a75d538c773709a15ebaf4e70299650026fce29ed05b382ad4a6cec96e426baba15e426879096e6ce2356b3955df2ccc36d64e4c0853376a26271fd977f053531a6b98c6096176c0208d229b2daad071d16d1379d6a48692b309057adbe3a605d5b0f54763da13b1d9ebf2c351875b770563f7ba9b73f839bf8ad9d6e14f1bea0de7ea252fad986d6287198aeb609a37c3400ce299825ab6d48ed3271cc4d59de1e8f42339a6925735128ff439ad4b41506d4a1d6e5aa5957c00f892ade1b4f8ed371698d7722fc64946a570b7fbf48aebad613afa9a2d65e7e4ac778bba56b831e66aee5114b4312c6212de68121815da004db847e5d9a5215d37b6036a63efd3eda3c2e57c1e4cd32ce3b356ba812521c104d9f6e3602c7613907ba2414a6fbf8690d9d95dc8039a4b9d5135b1c8cd71cfa9ff826df4866aebc98b156d188f8bcd08d89e9c5218ee4d87b488378cead92d2cb0ae275589e17914521fbf3be768f89cd0fc4602a71d0e6d30f04f73613a8b717941fd1547297d662cb11dde3de2d000395fd1a0c26d9410c6019f6dae9f71baf9e6ca78de4b3e3fde220e793da35eedf333d0e9584312c4ab98a4efa15d518086d04958918c672a88de4b33dbb4ad88eb6f13e64b9be2452b00c014d59c452b6e952e262f46378d3ef98e6d1043563b6a57d53912f78bb1d57c62e757a60d90c85c5d146da8c78b4286663b4fc61ea57f2675b30ae2148a218766612db13a23257d3a5b6111aa044cadb50d14c2f8ea84f13761adb062b46ec151598adeb58bf98d7161a283ba99f9849728b48b39375a29351bfe49db0fafa07e479f0210803b7c18030605d50033527ed4810c9cf04e1331a93a0f554882297b12072a815e8cf677ed81f02270149efc11adcf3414305afd2114596a9001e1dfd5483ab2a4e1b3b82b3d56f276174c87ef66b29396d3e1f23780a645c8d5f7da15cd30d56d4c82b7bd71dd0d365d9ce3310d9bf22782a196d896592dd932f7f74c3f2e1ae06292b190687f1c99ce4a6bad2590a2390d01a242a714ea77b8ed26f61afd2b74f004458fb138c618b7a9f054a58238afd10b9bb90fb2de5ad3b561271566f85426c1ce06110ab9fb7c74439295ea0fa4bd94b72946081f175610e5c87d5065ad3c1752bc8d8378e5cbe0afc1baedd4d04ae345bc1d0f69618c6a1bca95eb7896c10eeff95d6c2362a0ca8314bd508359c571705a181c64a8019b385d6d2b955cb3ccee8d1d28f8f122df94579ec8acf1a69f2c9444fa79b21b4751cec4a95d89a9c18917569935d9f53b469e37f6162ccd56388bb1e7fe71988332f4d32534d21d33412106052429fb251059db45c7805902e42e7792d5dfb4fa1df591dc384dc8e801b2368572f22946f7f8e0cefb608f1aca5880a32badcdea1150db747b3580a35bb30bced92582b3877df2f5126560d37a1cd735a67405cab4ef4b786e33bce08fd033f1ec4a47b1391b27b8fc9b447cce8e49442ab66bba0a9158acc38c305b65590f427ff4579e1fdaf52dfde9ae976c921df23d05980a5856c269060c355e8351b4d305489a02e383a87c49c7b54fa658b3dcf080f58283c712b58ebded7617ef199806fc391da241f7e3005476cb6d4a66d6510efac02e3169aea0401d0dddd6a70414e309130206521517c7f850df81002777614e2c263c7b0db2fe8cde26ec03f477c47715c402e49f50a4487110f42e1903d910663dbeaf05f486827a0f46b6f43426b4f0e7a7f9c4f186aceada897cceab8f7b1100a46815ef907f91ffdca41ded23aa72e4e294fe149c6831ad783333aa8f1c56e1f8b4188f4a07dcf534e4e7e4aecb41576c7aea01e8d8368d6e1761be53571bf1193667bf8d7db0fcfd2183776dc0d7c293439fd73f8673c63f62905106e51d047a445f27ecbd3e7dc6d66054f2be656e9e30f4e9d612ed3491c9a66bea1c660b46c2d826e8190010854bd0301e8f8357c7880aff47b7d5fa9d4af5a817c3c6ab5c6515f6bd20ad26ca7759124cfb6b2574bdad9e2a0b1585a918eef1fa727d9594b0b660023cf5bf674f72a3e6c8d221d9b379fab729298c2b64c6d94fd93318ab68761ee920ff2ae8b04e839ae8ccf08ba74f9b85b3cc213f3ac12f733b88c0783b0eca5a4f9baa0dc7f962b51289d76edaf65a37a35a1b13960c5f225d7d0c80c49c7bcb47fa1511332d5cc79959cb9f086f30f6854ba59a54b2d47936698bb740017e35787a0dd4af2ed9b8cda8fbdb5972c867e89ef2087a6428821bfe53ca091c83d74c89ab000f3f0f66b8bf815a8298d26fc1308368ae3debd729a302b593d95ed8b721edd5755f8ff94334fda846b7b0a195e391f1d64b1eb00ee0cd07eceeb794c40e16004f10435a4c170c27229c1048e30b8501d856848610fcb194d1a93e95cf9c7cf4a9bb5d4968cca6ca2bbe882330a38164d9870ace0906bdc9d846e8876f5d4304dd366f3591033148bb3dfee22c109de94c38e0e9d4c7995f9e39c4706a04f623e604bf21de285ce2bcc860a75e61053febc11d700cbc5a742b85854830c77ad594c004afc26fae1139ddd6bc3c7bc73a65686ce89a95b02de11fb8b8cf9d6ba19c6ac9dea27a53cf38bfcdba7eabcf53b0df6a9d6e2802f09b7b7cc9f0193ed7ed63b48c284cbcf00625a87ec7e059277b120b07b1c93c180a9b67ae90faaf0ffb7596abac4150d069fe0fe1d6b0fe945daee9b2d40a6833dd5a4339ef8bf80eb5434edc218f7e9ab0ca48ca30ba05029744834767cfbff4c51c389ab662e34bd3e253e129eaca62a96e44fc3e2ec1a651734969b4e7e28db357cdcf538c436fa9464d456d110219f393cb8dcf1bc8c3cb4a453361ea638a2ec8ef6594dd66eb47af65056d308dfea31c067bd6ffc3664291f1836f86260cc19396de089acabe09da11ed7d7909520d77b9f4bce94f581db1c6f95647bf11251f6168e93d0ad6bc7874d43ecb2aeb32c6e3971f4a58ca5ee05ed76d0e063a1d6170f98cc72b509a3c5bc184091eb21148cd3f26dd9a98dd439588654fea53b007aa76737a9db4c206379891c68c0ad6c87f2fb8526a61cc393d192af95caac8716b1047d46373d2c85a5731384cea8643de65c1616ad0353f96149a4035d8e8d58778d667dfbc36bac60251ff36b1f41e89d21ae1a79a9cd78725cd9635fdddef80f1acaaaf7f47f53fa13db13130f2837582896aa6f9fe922b6515dc19ecd0c73b0cef9be173d2fd109061f72b0cbd4e43c2693c55edd7c18762c0b70a16ba7c27a06d69da5cf24787ca105a88f6af7825a75ef627d2cf46e7878cb5e4d216f931410f8db72d49ca9071b1088c30e38f48e1b0792ab2a529d21a28de86df75cd9d8c3b0aedb3c496c425684792214f8f8990fce7c225139a0b68456012e206ee9b01fb9bf6e9c996ff83dfd480b130bf3479c1883c849af42656e097da9ed654f39c09d456b512653ead31973e814b83abc8da8ad59052fc19d8d70af476eff04b5d723807bf59ad3a6a9cfcd10398ab8e306ef7bcac481153295308d2d5bcbb5f7e3bec55ae4406beef82215abe3b4fa131e11d07a5aa9bfb77eb08313a65a8b2265b66400a57968077a1737255d1215e6c4b2ab073aced5aec3a3586ae6e8bf749de9598015c9a390ac548adc0c93f123fc98ddae8b2fe76f30d0893f989377daa0ffacbd9c3ea0834aec9a0e6d998ef3c6b2bfb6bb750470007c80aadb3862e0620c8e149f56c93e9b4ff7c5167ca13412df97ff1336767c0bdd57696ac7cb33f408f99271143cb0ba08a93c7c1ef260085bab767f394ccaa474b330ad9ad77fb8f24de5c75aad91d6ec566e2ea8a56e3b1a06a378fd14900611d27fb2940adc77b7f959c362326eba445156a5d8ce5721602412375677cf3f9e88cd257e8ff0b60df26b5674071a71f0beef3deb6f2e7974122d1dafe2ddfbc2e78de11ff409ad4256766ef451bd9a2b353160eba026d2a23c990e2e5a97b34c229dbe8524e8d5f98cf0d3149a4c4ab1ac6610fc099abc04df20be4837ff1662bb5e4d978078095d22452f52a5b05877ce6e886a7aa60f5151a2c270ddabc8cb0f98f70be7486050f7c5bc54de353a769a141a068fbb8eb2fffc78eee829ac1dbcecc3c210da26e288746f072dd7b78733901d61e2c4669115d6b5bdc34554eb58f4d04633b22f3e9a016828e9cacc1aa80804f5951f60295b037c4363e39a60aa08ae0ba363b91df22273e8b48dbd34f93b6db55d3a78a8b6999b58955ec3d8a74161926d87fe5b167a0ac6ee89f4a3953c2e7a6dd053516f1386dffe691578f82ce02316ef72f1b524c1fab0076416eb79476682d26f63edeb159d05aa3e086fb00c2bd598e8ab67be9c00f2bfe0c646d6bd84701ad62aad5e3affa9d3396344c93f1b989202cd1ee50347247cc10349ea215a8a632f62e510d8a7f9ae7087859cec603ef8b986f1c3d55dcc88c66c45a4a8ca524e3f3c6d7ad6c4a15ce0a9323eddc585cdcc9e427c0a8583d82eace65fde74c691efd6c595004cd6cc627e50e64e7ba025b4dc7d74ab043acaa1dfcccae97398bf789219917d21205da168d3c9041c8c4a3fdcd082be58906faba4cf2b8cca21235b46d5f429e21aac5bfc6a8ed847bdd8ec7f7b45ff7b06d8e1739249a9f38a29b2bccc10187422e694b29f548940aaa6e92c334f79b9975982fcabd77963e026e6177995cb267cfd2050236a9f57fb191fe037ea0e8446624c9a33763dab46867cd10a28e64c71b96360a72c3183d9d28ea504aaf6e50f18d12b81e3de61e82146f43d397f40e54ba9aaeabb7d9fa7db299e3f574cc9db9693a5d73d65c53c4473998f63e377cdd94df7d2ed8a4c379657d506141b6e179c03f7bde455fadf6ab0b6d673aa033a3ddf65af67d67852cf294bb2d4b03558216cc3bdb1b00eaed1c0a592eb46d8f6364fb9c4cd9e30d7ca9821720b0dabe5d981eb5d035871118573fbd2b0e9040c5b63b83653825c7fab9e083fc971f12ff3e97d9852053c450d0e5662169b0b246259b3e58ed965bce5c77af19572077f6adbc06e6345e9bcd1262e993a41b6ecdcdb7997e3d53611791dc928a13df5a2edcbc1294dcef1832a2f750c02b318ca5d940c2f4b7c928018a5092d14b99e2bb8dc5ae72ee87172a68215a50d6f8007c20fb98835ef39186dc21b09b6a5e00d1e5b21e482b8290d1bb54d2796a035161eb3349be086cf127691cdd82e29721af28360f10a027db6d96324146254c894f32bc515ee0b7770fcaef664cea073c4613bf4ba257d1407500d327590e0e836cdfb9f81a74835f20c14c4b5ac0e297eb34f8f0a080040b97de452f3058bc87ee367b7d606260653ff40dc32a80c1185a6abce080373a993826ceaed51955cacea5c1d0b7816b038991b241975a9a024b0995bcb2f3383d93a853c43808add463189cb9e563d96c452837e08fe7e71edf2b7f27207099063b1cfc06de0734d16b230aada8a9660080e44fa40bc3d9cbd774992b264d477474bec1ff8906c8d12d1d39ebebef37dd5d174f805feaa3eaf4e8f91589b54973e94974bbde7ec759589fb2c1612366be0f3e41b831a51897491ad30000"
)]
		public void ElementsTxHashCorrectness(string expectedHash, string txhex)
		{
			var tx = Transaction.Parse(txhex, Liquid.Instance.Regtest);
			Assert.Equal(expectedHash, tx.GetHash().ToString());
			tx.PrecomputeHash(true, true);
			Assert.Equal(expectedHash, tx.GetHash().ToString());
			tx.PrecomputeHash(true, false);
			Assert.Equal(expectedHash, tx.GetHash().ToString());
			tx = Transaction.Load(tx.ToBytes(), Liquid.Instance.Regtest);

			Assert.Equal(expectedHash, tx.GetHash().ToString());
			tx.PrecomputeHash(true, true);
			Assert.Equal(expectedHash, tx.GetHash().ToString());
			tx.PrecomputeHash(true, false);
			Assert.Equal(expectedHash, tx.GetHash().ToString());

		}

		[Fact(Timeout = 30000)]
		public async Task CanSyncSlimChain()
		{
			using var builder = NodeBuilderEx.Create();
			var node = builder.CreateNode();
			builder.StartAll();
			node.Generate(100);

			var slimChain = new SlimChain(builder.Network.GenesisHash);
			var userAgent = "NBXplorer-" + RandomUtils.GetInt64();

			async Task Connect()
			{
				var ourNode = await Node.ConnectAsync(builder.Network, node.NodeEndpoint,
					new NodeConnectionParameters()
					{
						UserAgent = userAgent,
						IsRelay = true
					});
				ourNode.VersionHandshake();
				try
				{
					ourNode.SynchronizeSlimChain(slimChain);
				}
				catch (Exception e)
				{
					Console.WriteLine(e);
				}

				ourNode.StateChanged += (node1, state) =>
				{
					if (!node1.IsConnected)
					{
						_ = Connect();
					}
				};
			}

			await Connect();
		}

		[Fact]
		public void CanSignTransactions()
		{
			using (var builder = NodeBuilderEx.Create())
			{
				var node = builder.CreateNode();
				builder.StartAll();
				node.Generate(builder.Network.Consensus.CoinbaseMaturity + 1);
				var rpc = node.CreateRPCClient();
				var alice = new Key().GetBitcoinSecret(builder.Network);
				BitcoinAddress aliceAddress = alice.GetAddress(ScriptPubKeyType.Legacy);
				var txid = rpc.SendToAddress(aliceAddress, Money.Coins(1.0m));
				var tx = rpc.GetRawTransaction(txid);
				var coin = tx.Outputs.AsCoins().First(c => c.ScriptPubKey == aliceAddress.ScriptPubKey);

				// Check the hash calculated correctly
				Assert.Equal(txid, tx.GetHash());
				TransactionBuilder txbuilder = builder.Network.CreateTransactionBuilder();
				txbuilder.AddCoins(coin);
				txbuilder.AddKeys(alice);
				txbuilder.Send(new Key(), Money.Coins(0.4m));
				txbuilder.SendFees(Money.Coins(0.001m));
				txbuilder.SetChange(aliceAddress);
				var signed = txbuilder.BuildTransaction(false);
				txbuilder.SignTransactionInPlace(signed);
				txbuilder.Verify(signed, out var err);
				Assert.True(txbuilder.Verify(signed));
				rpc.SendRawTransaction(signed);

				// Let's try P2SH with 2 coins
				aliceAddress = alice.PubKey.ScriptPubKey.Hash.GetAddress(builder.Network);
				txid = rpc.SendToAddress(aliceAddress, Money.Coins(1.0m));
				tx = rpc.GetRawTransaction(txid);
				coin = tx.Outputs.AsCoins().First(c => c.ScriptPubKey == aliceAddress.ScriptPubKey);

				txid = rpc.SendToAddress(aliceAddress, Money.Coins(1.0m));
				tx = rpc.GetRawTransaction(txid);
				var coin2 = tx.Outputs.AsCoins().First(c => c.ScriptPubKey == aliceAddress.ScriptPubKey);

				txbuilder = builder.Network.CreateTransactionBuilder()
								.AddCoins(new[] { coin.ToScriptCoin(alice.PubKey.ScriptPubKey), coin2.ToScriptCoin(alice.PubKey.ScriptPubKey) })
								.AddKeys(alice)
								.SendAll(new Key())
								.SendFees(Money.Coins(0.00001m))
								.SubtractFees()
								.SetChange(aliceAddress);

				signed = txbuilder.BuildTransaction(false);
				txbuilder.SignTransactionInPlace(signed);
				txbuilder.Verify(signed, out err);
				Assert.True(txbuilder.Verify(signed));
				rpc.SendRawTransaction(signed);
			}
		}

		[Fact]
		[Obsolete]
		public void GetSignerDontCrash()
		{
			var tx = Transaction.Parse("0100000001f227bcc52b801ac8459697d01c39da605b76fba2299c27b76de7e89ea385557b000000006441dd1908a47465e173f87c896987f1dcf67d14d04780a64f59d7338875fda2b06950bb53c234b7230ef8473af7fb668e31cf93ab71f40902cc7ca8837529970af64121033c34ca37a97d641a5d21761785092ad22e2f31e9f1820a7a10cdae88b131e303ffffffff028dce9700000000001976a91453e6c0067169defd9553b365337ea2e77c55d70288ac10270000000000001976a914d9fcb8501befb3e8a8f2391a33419edbe67d0f9a88ac00000000", NBitcoin.Altcoins.BCash.Instance.Testnet);
			foreach (var item in tx.Inputs)
			{
				item.GetSigner();
			}
		}

		[Fact]
		public void CanParseAddress()
		{
			using (var builder = NodeBuilderEx.Create())
			{
				var node = builder.CreateNode();
				builder.StartAll();
				var addr = node.CreateRPCClient().SendCommand(RPC.RPCOperations.getnewaddress).Result.ToString();
				var addr2 = BitcoinAddress.Create(addr, builder.Network).ToString();
				Assert.Equal(addr, addr2);

				var address = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, builder.Network);

				// Test normal address
				var isValid = ((JObject)node.CreateRPCClient().SendCommand("validateaddress", address.ToString()).Result)["isvalid"].Value<bool>();
				Assert.True(isValid);

				// Test p2sh
				address = new Key().PubKey.ScriptPubKey.Hash.ScriptPubKey.GetDestinationAddress(builder.Network);
				isValid = ((JObject)node.CreateRPCClient().SendCommand("validateaddress", address.ToString()).Result)["isvalid"].Value<bool>();
				Assert.True(isValid);
			}
		}

		/// <summary>
		/// This test check if we can scan RPC capabilities
		/// </summary>
		[Fact]
		public void DoesRPCCapabilitiesWellAdvertised()
		{
			using (var builder = NodeBuilderEx.Create())
			{
				var node = builder.CreateNode();
				builder.StartAll();
				node.Generate(node.Network.Consensus.CoinbaseMaturity + 1);
				var rpc = node.CreateRPCClient();
				rpc.ScanRPCCapabilities();
				Assert.NotNull(rpc.Capabilities);

				CheckCapabilities(rpc, "getnetworkinfo", rpc.Capabilities.SupportGetNetworkInfo);
				CheckCapabilities(rpc, "scantxoutset", rpc.Capabilities.SupportScanUTXOSet);
				CheckCapabilities(rpc, "signrawtransactionwithkey", rpc.Capabilities.SupportSignRawTransactionWith);
				CheckCapabilities(rpc, "estimatesmartfee", rpc.Capabilities.SupportEstimateSmartFee);

				try
				{
					var address = rpc.GetNewAddress(new GetNewAddressRequest()
					{
						AddressType = AddressType.Bech32
					});
					// If this fail, rpc support segwit but you said it does not
					Assert.Equal(rpc.Capabilities.SupportSegwit, address.ScriptPubKey.IsScriptType(ScriptType.Witness));
					if (rpc.Capabilities.SupportSegwit)
					{
						Assert.True(builder.Network.Consensus.SupportSegwit, "The node RPC support segwit, but Network.Consensus.SupportSegwit is set to false");
						rpc.SendToAddress(address, Money.Coins(1.0m));
					}
					else
					{
						Assert.False(builder.Network.Consensus.SupportSegwit, "The node RPC does not support segwit, but Network.Consensus.SupportSegwit is set to true (This error can be normal if you are using a old node version)");
					}
#if HAS_SPAN
					if (rpc.Capabilities.SupportTaproot)
					{
						Assert.True(builder.Network.Consensus.SupportTaproot, "The node RPC support taproot, but Network.Consensus.SupportTaproot is set to false");
						rpc.SendToAddress(new Key().GetAddress(ScriptPubKeyType.TaprootBIP86, rpc.Network), Money.Coins(1.0m));
					}
					else
					{
						Assert.False(builder.Network.Consensus.SupportTaproot, "The node RPC does not support segwit, but Network.Consensus.SupportSegwit is set to true (This error can be normal if you are using a old node version)");
					}
#endif
				}
				catch (RPCException) when (!rpc.Capabilities.SupportSegwit)
				{
				}
			}
		}
		private void CheckCapabilities(Action command, bool supported)
		{
			if (!supported)
			{
				var ex = Assert.Throws<RPCException>(command);
				Assert.True(ex.RPCCode == RPCErrorCode.RPC_METHOD_NOT_FOUND || ex.RPCCode == RPCErrorCode.RPC_METHOD_DEPRECATED);
			}
			else
			{
				try
				{
					command();
				}
				catch (RPCException ex) when (ex.RPCCode != RPCErrorCode.RPC_METHOD_NOT_FOUND && ex.RPCCode != RPCErrorCode.RPC_METHOD_DEPRECATED)
				{
					// Method exists
				}
			}
		}
		private void CheckCapabilities(RPCClient rpc, string command, bool supported)
		{
			CheckCapabilities(() => rpc.SendCommand(command, "random"), supported);
		}

		[Fact]
		public void CanSyncWithPoW()
		{
			using (var builder = NodeBuilderEx.Create())
			{
				if (IsElements(builder.Network))
				{
					//no pow in liquid
					return;
				}
				var node = builder.CreateNode();
				builder.StartAll();
				node.Generate(100);

				var nodeClient = node.CreateNodeClient();
				nodeClient.VersionHandshake();
				ConcurrentChain chain = new ConcurrentChain(builder.Network);
				nodeClient.SynchronizeChain(chain, new Protocol.SynchronizeChainOptions() { SkipPoWCheck = false });
				Assert.Equal(100, chain.Height);
			}
		}

		[Fact]
		public async Task CanSignAltcoinTransaction()
		{
			using (var builder = NodeBuilderEx.Create())
			{
				var node = builder.CreateNode();
				builder.StartAll();
				var rpc = node.CreateRPCClient();
				rpc.Generate(builder.Network.Consensus.CoinbaseMaturity + 1);
				var key = new Key();
				var addr = key.GetAddress(ScriptPubKeyType.Legacy, builder.Network);
				var txid = await rpc.SendToAddressAsync(addr, Money.Coins(1.0m));
				var tx = await rpc.GetRawTransactionAsync(txid);
				var dest = await rpc.GetNewAddressAsync();
				var txbuilder = builder.Network.CreateTransactionBuilder();
				txbuilder.AddCoins(tx.Outputs.AsCoins());
				txbuilder.AddKeys(key);
				txbuilder.Send(dest, Money.Coins(1.0m));
				txbuilder.SendFees(Money.Coins(0.00004m));
				txbuilder.SubtractFees();
				var signed = txbuilder.BuildTransaction(true);
				Assert.True(txbuilder.Verify(signed));
				await rpc.SendRawTransactionAsync(signed);
			}
		}

		[Fact]
		public void CheckForkIdIsUsedDuringSigning()
		{
			var n = Altcoins.AltNetworkSets.BCash.Regtest;
			var key = new Key(Encoders.Hex.DecodeData("1718bce503a08a80ab698ad6e9b5211ed7a232958532877dd1cc67213fa5c4d9"));
			var dest = BitcoinAddress.Create("bchreg:qz8d85evkscf7qx3y7pycllwwpyjy8dj7uwryky89l", n);
			var tx = Transaction.Parse("0200000001766dc30996037844f838a4fc2f214e1fc13c3d65d46221e4d915c8b9b1c002bc0000000048473044022076c1ff7362f4cb50d4a36fb9a72db9ac8c8de4f7de1d9145e8bf8fb667578f4b022051a774e17cd8a0e3d3862985bc860c8fd49202b424d78a3989fae2a3848ba06441feffffff0241101024010000001976a9141686731726f06127a4e0d33a90d7912055582eb188ac00e1f505000000001976a9147bf316ee14ba66ba07bbcaa9ad5d94515acf35fc88ac65000000", n);

			var txbuilder = n.CreateTransactionBuilder();
			txbuilder.AddCoins(tx.Outputs.AsCoins());
			txbuilder.AddKeys(key);
			txbuilder.Send(dest, Money.Coins(1.0m));
			txbuilder.SendFees(Money.Coins(0.00004m));
			txbuilder.SubtractFees();
			var signed = txbuilder.BuildTransaction(true);
			var expected = Transaction.Parse("0100000001557fec4d75208907d177542573a31aad7d5e825514c54d5e97ddb159a9621477010000006a473044022053ae899e9927f3f77c9aff605bd88b6b84205c290f8498bdba73ad063107a5e7022006357498990b46475f65045a74eb25645d4dee0835e108b11a87ce77ffd3348341210309046b4af074cfb8138abd6d87003398d497336d2aca5102393aff1f47c6c009ffffffff0160d1f505000000001976a9148ed3d32cb4309f00d127824c7fee7049221db2f788ac00000000", n);
			Assert.True(txbuilder.Verify(signed));
			Assert.Equal(expected.ToString(), signed.ToString());
		}

		[Fact]
		public async Task CorrectCoinMaturity()
		{
			using (var builder = NodeBuilderEx.Create())
			{
				var node = builder.CreateNode();
				builder.StartAll();
				node.Generate(builder.Network.Consensus.CoinbaseMaturity);
				var rpc = node.CreateRPCClient();
				if (IsElements(node.Network))
				{
					Assert.Contains((await rpc.GetBalancesAsync()),
						pair => pair.Value == Money.FromUnit(2100000, MoneyUnit.BTC));
					node.Generate(1);
					Assert.Contains((await rpc.GetBalancesAsync()),
						pair => pair.Value == Money.FromUnit(2100000, MoneyUnit.BTC));
				}
				else
				{
					Assert.Equal(Money.Zero, await rpc.GetBalanceAsync());
					node.Generate(1);
					Assert.NotEqual(Money.Zero, await rpc.GetBalanceAsync());
				}
			}
		}

		[Fact]
		public void CanSyncWithoutPoW()
		{
			using (var builder = NodeBuilderEx.Create())
			{
				var node = builder.CreateNode();
				builder.StartAll();
				node.Generate(100);
				var nodeClient = node.CreateNodeClient();
				nodeClient.VersionHandshake();
				ConcurrentChain chain = new ConcurrentChain(builder.Network);
				nodeClient.SynchronizeChain(chain, new Protocol.SynchronizeChainOptions() { SkipPoWCheck = true });
				Assert.Equal(node.CreateRPCClient().GetBestBlockHash(), chain.Tip.HashBlock);
				Assert.Equal(100, chain.Height);

				// If it fails, override Block.GetConsensusFactory()
				var b = node.CreateRPCClient().GetBlock(50);
				Assert.Equal(b.WithOptions(TransactionOptions.Witness).Header.GetType(), chain.GetBlock(50).Header.GetType());

				var b2 = nodeClient.GetBlocks(new Protocol.SynchronizeChainOptions() { SkipPoWCheck = true }).ToArray()[50];
				Assert.Equal(b2.Header.GetType(), chain.GetBlock(50).Header.GetType());
			}
		}

		private bool IsElements(Network nodeNetwork)
		{
			return nodeNetwork.NetworkSet == Altcoins.Liquid.Instance;
		}

		[Fact]
		public void GIVEN_Digibyte_WHEN_ProvidedAddressForSpecificNetwork_THEN_ShouldParseAddress()
		{
			// Main net
			Network mainnet = AltNetworkSets.DigiByte.Mainnet;
			var mainnetAddress = "DSdh4rXmRZizpZh7zKGSsyMqHmFE137G96";

			var mainnetAddressParsed = BitcoinAddress.Create(mainnetAddress, mainnet);
			Assert.NotNull(mainnetAddressParsed);
			Assert.NotNull(mainnetAddressParsed.ScriptPubKey);

			// Testnet
			Network testnet = AltNetworkSets.DigiByte.Testnet;
			var testnetAddress = "dgbt1q6j07ktmykusmkj6qng2x2cs6a4hlnn9d8h3s02";

			var testnetAddressParsed = BitcoinAddress.Create(testnetAddress, testnet);
			Assert.NotNull(testnetAddressParsed);
			Assert.NotNull(testnetAddressParsed.ScriptPubKey);
		}
	}
}
